<?php

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Loader
 * @subpackage Autoloader
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @version    $Id: Autoloader.php 25024 2012-07-30 15:08:15Z rob $
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
/** Zend_Loader */
require_once 'Zend/Loader.php';

/**
 * Autoloader stack and namespace autoloader
 *
 * @uses       Zend_Loader_Autoloader
 * @package    Zend_Loader
 * @subpackage Autoloader
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
if (!class_exists('Zend_Loader_Autoloader')) {///Fix bug optimizer+

	class Zend_Loader_Autoloader {

		/**
		 * @var Zend_Loader_Autoloader Singleton instance
		 */
		protected static $_instance;

		/**
		 * @var array Concrete autoloader callback implementations
		 */
		protected $_autoloaders = array();

		/**
		 * @var array Default autoloader callback
		 */
		protected $_defaultAutoloader = array('Zend_Loader', 'loadClass');

		/**
		 * @var bool Whether or not to act as a fallback autoloader
		 */
		protected $_fallbackAutoloader = false;

		/**
		 * @var array Callback for internal autoloader implementation
		 */
		protected $_internalAutoloader;

		/**
		 * @var array Supported namespaces 'Zend' and 'ZendX' by default.
		 */
		protected $_namespaces = array(
			'Zend_' => true,
			'ZendX_' => true,
		);

		/**
		 * @var array Namespace-specific autoloaders
		 */
		protected $_namespaceAutoloaders = array();

		/**
		 * @var bool Whether or not to suppress file not found warnings
		 */
		protected $_suppressNotFoundWarnings = false;

		/**
		 * @var null|string
		 */
		protected $_zfPath;

		/**
		 * Retrieve singleton instance
		 *
		 * @return Zend_Loader_Autoloader
		 */
		public static function getInstance() {
			if (null === self::$_instance) {
				self::$_instance = new self();
			}
			return self::$_instance;
		}

		/**
		 * Reset the singleton instance
		 *
		 * @return void
		 */
		public static function resetInstance() {
			self::$_instance = null;
		}

		/**
		 * Autoload a class
		 *
		 * @param  string $class
		 * @return bool
		 */
		public static function autoload($class) {
			$self = self::getInstance();

			foreach ($self->getClassAutoloaders($class) as $autoloader) {
				if ($autoloader instanceof Zend_Loader_Autoloader_Interface) {
					if ($autoloader->autoload($class)) {
						return true;
					}
				} elseif (is_array($autoloader)) {
					if (call_user_func($autoloader, $class)) {
						return true;
					}
				} elseif (is_string($autoloader) || is_callable($autoloader)) {
					if ($autoloader($class)) {
						return true;
					}
				}
			}

			return false;
		}

		/**
		 * Set the default autoloader implementation
		 *
		 * @param  string|array $callback PHP callback
		 * @return void
		 */
		public function setDefaultAutoloader($callback) {
			if (!is_callable($callback)) {
				throw new Zend_Loader_Exception('Invalid callback specified for default autoloader');
			}

			$this->_defaultAutoloader = $callback;
			return $this;
		}

		/**
		 * Retrieve the default autoloader callback
		 *
		 * @return string|array PHP Callback
		 */
		public function getDefaultAutoloader() {
			return $this->_defaultAutoloader;
		}

		/**
		 * Set several autoloader callbacks at once
		 *
		 * @param  array $autoloaders Array of PHP callbacks (or Zend_Loader_Autoloader_Interface implementations) to act as autoloaders
		 * @return Zend_Loader_Autoloader
		 */
		public function setAutoloaders(array $autoloaders) {
			$this->_autoloaders = $autoloaders;
			return $this;
		}

		/**
		 * Get attached autoloader implementations
		 *
		 * @return array
		 */
		public function getAutoloaders() {
			return $this->_autoloaders;
		}

		/**
		 * Return all autoloaders for a given namespace
		 *
		 * @param  string $namespace
		 * @return array
		 */
		public function getNamespaceAutoloaders($namespace) {
			$namespace = (string) $namespace;
			if (!array_key_exists($namespace, $this->_namespaceAutoloaders)) {
				return array();
			}
			return $this->_namespaceAutoloaders[$namespace];
		}

		/**
		 * Register a namespace to autoload
		 *
		 * @param  string|array $namespace
		 * @return Zend_Loader_Autoloader
		 */
		public function registerNamespace($namespace) {
			if (is_string($namespace)) {
				$namespace = (array) $namespace;
			} elseif (!is_array($namespace)) {
				throw new Zend_Loader_Exception('Invalid namespace provided');
			}

			foreach ($namespace as $ns) {
				if (!isset($this->_namespaces[$ns])) {
					$this->_namespaces[$ns] = true;
				}
			}
			return $this;
		}

		/**
		 * Unload a registered autoload namespace
		 *
		 * @param  string|array $namespace
		 * @return Zend_Loader_Autoloader
		 */
		public function unregisterNamespace($namespace) {
			if (is_string($namespace)) {
				$namespace = (array) $namespace;
			} elseif (!is_array($namespace)) {
				throw new Zend_Loader_Exception('Invalid namespace provided');
			}

			foreach ($namespace as $ns) {
				if (isset($this->_namespaces[$ns])) {
					unset($this->_namespaces[$ns]);
				}
			}
			return $this;
		}

		/**
		 * Get a list of registered autoload namespaces
		 *
		 * @return array
		 */
		public function getRegisteredNamespaces() {
			return array_keys($this->_namespaces);
		}

		public function setZfPath($spec, $version = 'latest') {
			$path = $spec;
			if (is_array($spec)) {
				if (!isset($spec['path'])) {
					throw new Zend_Loader_Exception('No path specified for ZF');
				}
				$path = $spec['path'];
				if (isset($spec['version'])) {
					$version = $spec['version'];
				}
			}

			$this->_zfPath = $this->_getVersionPath($path, $version);
			set_include_path(implode(PATH_SEPARATOR, array(
						$this->_zfPath,
						get_include_path(),
					)));
			return $this;
		}

		public function getZfPath() {
			return $this->_zfPath;
		}

		/**
		 * Get or set the value of the "suppress not found warnings" flag
		 *
		 * @param  null|bool $flag
		 * @return bool|Zend_Loader_Autoloader Returns boolean if no argument is passed, object instance otherwise
		 */
		public function suppressNotFoundWarnings($flag = null) {
			if (null === $flag) {
				return $this->_suppressNotFoundWarnings;
			}
			$this->_suppressNotFoundWarnings = (bool) $flag;
			return $this;
		}

		/**
		 * Indicate whether or not this autoloader should be a fallback autoloader
		 *
		 * @param  bool $flag
		 * @return Zend_Loader_Autoloader
		 */
		public function setFallbackAutoloader($flag) {
			$this->_fallbackAutoloader = (bool) $flag;
			return $this;
		}

		/**
		 * Is this instance acting as a fallback autoloader?
		 *
		 * @return bool
		 */
		public function isFallbackAutoloader() {
			return $this->_fallbackAutoloader;
		}

		/**
		 * Get autoloaders to use when matching class
		 *
		 * Determines if the class matches a registered namespace, and, if so,
		 * returns only the autoloaders for that namespace. Otherwise, it returns
		 * all non-namespaced autoloaders.
		 *
		 * @param  string $class
		 * @return array Array of autoloaders to use
		 */
		public function getClassAutoloaders($class) {
			$namespace = false;
			$autoloaders = array();

			// Add concrete namespaced autoloaders
			foreach (array_keys($this->_namespaceAutoloaders) as $ns) {
				if ('' == $ns) {
					continue;
				}
				if (0 === strpos($class, $ns)) {
					if ((false === $namespace) || (strlen($ns) > strlen($namespace))) {
						$namespace = $ns;
						$autoloaders = $this->getNamespaceAutoloaders($ns);
					}
				}
			}

			// Add internal namespaced autoloader
			foreach ($this->getRegisteredNamespaces() as $ns) {
				if (0 === strpos($class, $ns)) {
					$namespace = $ns;
					$autoloaders[] = $this->_internalAutoloader;
					break;
				}
			}

			// Add non-namespaced autoloaders
			$autoloadersNonNamespace = $this->getNamespaceAutoloaders('');
			if (count($autoloadersNonNamespace)) {
				foreach ($autoloadersNonNamespace as $ns) {
					$autoloaders[] = $ns;
				}
				unset($autoloadersNonNamespace);
			}

			// Add fallback autoloader
			if (!$namespace && $this->isFallbackAutoloader()) {
				$autoloaders[] = $this->_internalAutoloader;
			}

			return $autoloaders;
		}

		/**
		 * Add an autoloader to the beginning of the stack
		 *
		 * @param  object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
		 * @param  string|array $namespace Specific namespace(s) under which to register callback
		 * @return Zend_Loader_Autoloader
		 */
		public function unshiftAutoloader($callback, $namespace = '') {
			$autoloaders = $this->getAutoloaders();
			array_unshift($autoloaders, $callback);
			$this->setAutoloaders($autoloaders);

			$namespace = (array) $namespace;
			foreach ($namespace as $ns) {
				$autoloaders = $this->getNamespaceAutoloaders($ns);
				array_unshift($autoloaders, $callback);
				$this->_setNamespaceAutoloaders($autoloaders, $ns);
			}

			return $this;
		}

		/**
		 * Append an autoloader to the autoloader stack
		 *
		 * @param  object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
		 * @param  string|array $namespace Specific namespace(s) under which to register callback
		 * @return Zend_Loader_Autoloader
		 */
		public function pushAutoloader($callback, $namespace = '') {
			$autoloaders = $this->getAutoloaders();
			array_push($autoloaders, $callback);
			$this->setAutoloaders($autoloaders);

			$namespace = (array) $namespace;
			foreach ($namespace as $ns) {
				$autoloaders = $this->getNamespaceAutoloaders($ns);
				array_push($autoloaders, $callback);
				$this->_setNamespaceAutoloaders($autoloaders, $ns);
			}

			return $this;
		}

		/**
		 * Remove an autoloader from the autoloader stack
		 *
		 * @param  object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
		 * @param  null|string|array $namespace Specific namespace(s) from which to remove autoloader
		 * @return Zend_Loader_Autoloader
		 */
		public function removeAutoloader($callback, $namespace = null) {
			if (null === $namespace) {
				$autoloaders = $this->getAutoloaders();
				if (false !== ($index = array_search($callback, $autoloaders, true))) {
					unset($autoloaders[$index]);
					$this->setAutoloaders($autoloaders);
				}

				foreach ($this->_namespaceAutoloaders as $ns => $autoloaders) {
					if (false !== ($index = array_search($callback, $autoloaders, true))) {
						unset($autoloaders[$index]);
						$this->_setNamespaceAutoloaders($autoloaders, $ns);
					}
				}
			} else {
				$namespace = (array) $namespace;
				foreach ($namespace as $ns) {
					$autoloaders = $this->getNamespaceAutoloaders($ns);
					if (false !== ($index = array_search($callback, $autoloaders, true))) {
						unset($autoloaders[$index]);
						$this->_setNamespaceAutoloaders($autoloaders, $ns);
					}
				}
			}

			return $this;
		}

		/**
		 * Constructor
		 *
		 * Registers instance with spl_autoload stack
		 *
		 * @return void
		 */
		protected function __construct() {
			spl_autoload_register(array(__CLASS__, 'autoload'));
			$this->_internalAutoloader = array($this, '_autoload');
		}

		/**
		 * Internal autoloader implementation
		 *
		 * @param  string $class
		 * @return bool
		 */
		protected function _autoload($class) {
			$callback = $this->getDefaultAutoloader();
			try {
				if ($this->suppressNotFoundWarnings()) {
					@call_user_func($callback, $class);
				} else {
					call_user_func($callback, $class);
				}
				return $class;
			} catch (Zend_Exception $e) {
				return false;
			}
		}

		/**
		 * Set autoloaders for a specific namespace
		 *
		 * @param  array $autoloaders
		 * @param  string $namespace
		 * @return Zend_Loader_Autoloader
		 */
		protected function _setNamespaceAutoloaders(array $autoloaders, $namespace = '') {
			$namespace = (string) $namespace;
			$this->_namespaceAutoloaders[$namespace] = $autoloaders;
			return $this;
		}

		/**
		 * Retrieve the filesystem path for the requested ZF version
		 *
		 * @param  string $path
		 * @param  string $version
		 * @return void
		 */
		protected function _getVersionPath($path, $version) {
			$type = $this->_getVersionType($version);

			if ($type == 'latest') {
				$version = 'latest';
			}

			$availableVersions = $this->_getAvailableVersions($path, $version);
			if (empty($availableVersions)) {
				throw new Zend_Loader_Exception('No valid ZF installations discovered');
			}

			$matchedVersion = array_pop($availableVersions);
			return $matchedVersion;
		}

		/**
		 * Retrieve the ZF version type
		 *
		 * @param  string $version
		 * @return string "latest", "major", "minor", or "specific"
		 * @throws Zend_Loader_Exception if version string contains too many dots
		 */
		protected function _getVersionType($version) {
			if (strtolower($version) == 'latest') {
				return 'latest';
			}

			$parts = explode('.', $version);
			$count = count($parts);
			if (1 == $count) {
				return 'major';
			}
			if (2 == $count) {
				return 'minor';
			}
			if (3 < $count) {
				throw new Zend_Loader_Exception('Invalid version string provided');
			}
			return 'specific';
		}

		/**
		 * Get available versions for the version type requested
		 *
		 * @param  string $path
		 * @param  string $version
		 * @return array
		 */
		protected function _getAvailableVersions($path, $version) {
			if (!is_dir($path)) {
				throw new Zend_Loader_Exception('Invalid ZF path provided');
			}

			$path = rtrim($path, '/');
			$path = rtrim($path, '\\');
			$versionLen = strlen($version);
			$versions = array();
			$dirs = glob("$path/*", GLOB_ONLYDIR);
			foreach ((array) $dirs as $dir) {
				$dirName = substr($dir, strlen($path) + 1);
				if (!preg_match('/^(?:ZendFramework-)?(\d+\.\d+\.\d+((a|b|pl|pr|p|rc)\d+)?)(?:-minimal)?$/i', $dirName, $matches)) {
					continue;
				}

				$matchedVersion = $matches[1];

				if (('latest' == $version)
						|| ((strlen($matchedVersion) >= $versionLen)
						&& (0 === strpos($matchedVersion, $version)))
				) {
					$versions[$matchedVersion] = $dir . '/library';
				}
			}

			uksort($versions, 'version_compare');
			return $versions;
		}

	}

}